
#include <QDir>
#include <QFileInfo>
#include "phonehandler.h"

#define VENDOR      0x1004
#define PRODUCT     0x6310

static const u8 dlModeSuccessResponse[] = { 0x7e, 0x02, 0x6a, 0xd3, 0x7e };

PhoneHandler::PhoneHandler(QObject *parent) : QObject( parent ),
    mode( NoDevice ),
    mutex( new QMutex( QMutex::Recursive ) ),
    devh( NULL )
{
}
PhoneHandler::~PhoneHandler()
{
    Close();
}

void PhoneHandler::Close()
{
    QMutexLocker locker( mutex );
    if( devh )
    {
        libusb_release_interface( devh, 0 );
        libusb_release_interface( devh, 2 );
        if( mode == Diag_Mode )
        {
            libusb_release_interface( devh, 3 );
        }
        libusb_close( devh );
        devh = NULL;
    }
    mode = NoDevice;
}

bool PhoneHandler::PollDownloadMode()
{
    QMutexLocker locker( mutex );
    u8* oBuff = (u8*)oBuff32;
    oBuff[ 0 ] = 0x7e;
    oBuff[ 1 ] = 0x06;
    oBuff[ 2 ] = 0x4e;
    oBuff[ 3 ] = 0x95;
    oBuff[ 4 ] = 0x7e;
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 5, &out2, e2Timeout );
    if( r < 0 || out != 5 )
    {
        if( r == LIBUSB_ERROR_NOT_FOUND )// no need to spit out warnings.  this just means it was disconnected or rebooted
        {
            return false;
        }
        WRN << "do_i3_bulk_transfer():" << r << out;
        return false;
    }

    if( memcmp( dlModeSuccessResponse, dBuff, 5 ) )
    {
        WRN << "unexpected DL mode response (" << out << ")";
        // it returns 0x130688d57e when you send this command and it is booted normally and DiagMode enabled
        hexdump( dBuff, 5 );
        return false;
    }
    return true;
}

bool PhoneHandler::NVIdentify()
{
    // sends the "Get MSL" command, and then send the msl to the phone
    u8* oBuff = (u8*)oBuff32;

    oBuff[ 0 ] = 0x11;
    oBuff[ 1 ] = 0x17;
    oBuff[ 2 ] = 0x00;
    oBuff[ 3 ] = 0x06;
    oBuff[ 4 ] = 0x62;
    oBuff[ 5 ] = 0x4f;
    oBuff[ 6 ] = 0x7e;
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 7, &out2, e2Timeout );
    if( r < 0 )
    {
        WRN << "get MSL failed" << r;
        return false;
    }
    //hexdump( &dBuff, out );

    // now send it back
    oBuff32[ 0 ] = qToBigEndian( 0x7e7e7e41 );
    memcpy( oBuff + 4, ((u8*)dBuff) + 4, 6 );
    oBuff[ 12 ] = 0x7e;
    DoFcs16( oBuff + 3, 7 );

    //DBG << "identify in:";
    //hexdump( oBuff, 13 );

    r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 13, &out2, e2Timeout );
    if( r < 0 || out != 5 )
    {
        WRN << "send MSL failed" << r;
        return false;
    }
    qDebug() << "Identify NV ok";
    //DBG << "identify out:";
    //hexdump( &dBuff, out );
    return true;
}

QByteArray PhoneHandler::ReadNVItem( u16 idx )
{
    QMutexLocker locker( mutex );
    u8* oBuff = (u8*)oBuff32;

    u32 s = 0x8b;
    u8 mBuf[ 0x8b ];

    // clear buffer
    memset( mBuf, 0, sizeof( mBuf ) );

    // index
    *(u16*)( mBuf + 4 ) = qToLittleEndian( idx );

    // escape it
    EscapeMessage( mBuf, oBuff, s );

    // get NV item request
    oBuff32[ 0 ] = qToBigEndian( 0x7e7e7e26 );

    // terminate it
    oBuff[ s - 1 ] = 0x7e;

    // create checksum
    DoFcs16( oBuff + 3, s - 6 );

    // send request
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, s, &out2, e2Timeout );
    if( r < 0 || out < 0x88 )
    {
        WRN << "do_i3_bulk_transfer():" << r << out;
        hexdump( oBuff, s );
        hexdump( (u8*)dBuff, out );
        return QByteArray();
    }

    UnEscapeMessage( (u8*)dBuff, out );
    if( !CheckFcs( (u8*)dBuff, out - 3 ) )
    {
        WRN << "checksum doesn\'t match";
        return QByteArray();
    }
    if( *(u16*)( ((u8*)dBuff) + 1 ) != qToLittleEndian( idx ) || *(u8*)dBuff != 0x26 )
    {
        WRN << "unexpected reply start";
        hexdump( (u8*)dBuff, out );
        return QByteArray();
    }

    // check response code
    char errorCode = *(((u8*)dBuff) + 0x80 + 3);
    switch( errorCode )
    {
    default:
        WRN << "Unhandred Diag response" << errorCode;
        break;
    case NV_ErrItemInactive:
        WRN << "Diag response NV_ErrItemInactive";
        break;
    case NV_ErrParameterBad:
        WRN << "Diag response NV_ErrParameterBad";
        break;
    case NV_OK:
        break;
    }


    return QByteArray( ((const char*)dBuff) + 3, out - 6 );
}

bool PhoneHandler::ReadNV_MEID( u64 &out )
{
    QByteArray ba = ReadNVItem( NV_MEID );
    if( ba.size() < (int)sizeof( u64 ) )
    {
        WRN << "ba.size() < sizeof( u64 )";
        return false;
    }
    out = qFromLittleEndian( *(quint64*)( ba.data() ) ) & 0x00ffffffffffffffull;// 56 bits
    return true;
}

bool PhoneHandler::ReadNV_BlueTooth( u64 &out )
{
    QByteArray ba = ReadNVItem( NV_BlueToothAddress );
    if( ba.size() < (int)sizeof( u64 ) )
    {
        WRN << "ba.size() < sizeof( u64 )";
        return false;
    }
    out = ( qFromBigEndian( (*(quint64*)( ba.data() )) & 0x0000ffffffffffffull ) ) >> 16;
    return true;
}

bool PhoneHandler::ReadNV_ESN( u32 &out )
{
    QByteArray ba = ReadNVItem( NV_ESN );
    if( ba.size() < (int)sizeof( u32 ) )
    {
        WRN << "ba.size() < sizeof( u32 )";
        return false;
    }
    out = qFromLittleEndian( *(quint32*)( ba.data() ) );
    return true;
}

QString PhoneHandler::ReadNV_MDN()
{
    QByteArray ba = ReadNVItem( NV_MDN );
    if( !ba.size() )
    {
        return QString();
    }
    return QString( ba.data() + 1 );
}

QString PhoneHandler::ReadNV_MSL()
{
    return QString( ReadNVItem( NV_MSL ) );
}

QString PhoneHandler::ReadNV_OTSL()
{
    return QString( ReadNVItem( NV_OneTimeSubsityLock ) );
}

QString PhoneHandler::ReadNV_UnlockCode()
{
    return QString( ReadNVItem( NV_UnlockCode ) );
}

bool PhoneHandler::SendKeyPress( u16 key )
{
    QMutexLocker locker( mutex );
    u8* oBuff = (u8*)oBuff32;

    u32 s = 6;
    u8 mBuf[ 8 ];

    // clear buffer
    memset( mBuf, 0, sizeof( mBuf ) );

    // key press request
    mBuf[ 0 ] = 0x20;

    // index
    *(u16*)( mBuf + 1 ) = qToBigEndian( key );

    // escape it
    EscapeMessage( mBuf, oBuff, s );

    // terminate it
    oBuff[ s - 1 ] = 0x7e;

    // create checksum
    DoFcs16( oBuff, s - 3 );
    //DBG << "input";
    //hexdump( oBuff, s );

    // send request
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, s, &out2, e2Timeout );

   // DBG << "output";
    //hexdump( (u8*)dBuff, out );
    if( r < 0 || out < 0x10 )
    {
        WRN << "do_i3_bulk_transfer():" << r << out;
        hexdump( oBuff, s );
        hexdump( (u8*)dBuff, out );
        return false;
    }

    UnEscapeMessage( (u8*)dBuff, out );
    if( !CheckFcs( (u8*)dBuff, out - 3 ) )
    {
        WRN << "checksum doesn\'t match";
        return false;
    }
    if( *(u8*)dBuff != 0x20 )
    {
        WRN << "unexpected reply start";
        hexdump( (u8*)dBuff, out );
        return false;
    }
    return true;
}

bool PhoneHandler::PollDiagMode()
{
    QMutexLocker locker( mutex );
    u32 req = qToBigEndian( 0x0c143a7e );
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, (u8*)&req, sizeof( u32 ), &out2, e2Timeout );
    if( r < 0 || out != 0x33 )
    {
        WRN << "do_i3_bulk_transfer():" << r << out;
        return false;
    }
    // not checking the values in this buffer for now.  the second u32 is a little endian 32bit number that
    // shows up in the screen of QPST with the phone in diag mode.  that same value shows up in LGNPST as the ESN
    return true;
}

bool PhoneHandler::RebootToDownloadMode()
{
    if( mode != Diag_Mode )
    {
        DBG << "Need to be in Diag Mode to call this function";
        return false;
    }

    int r;
    QMutexLocker locker( mutex );
    u32 req = qToBigEndian( 0x3aa16e7e );

    r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, (u8*)&req, sizeof( u32 ), &out2, e2Timeout );
    if( r < 0 || out != 4 )
    {
        WRN << "do_i3_bulk_transfer():" << r << out;
        return false;
    }
    if( dBuff[ 0 ] != req )
    {
        WRN << "Boot into DL mode didn\'t return 0x3aa16e7e" << ::hex << qFromBigEndian( dBuff[ 0 ] );
        return false;
    }
    Close();
    return true;
}

bool PhoneHandler::RebootPhone()
{
    if( mode != DL_Mode )
    {
        DBG << "Need to be in Download Mode to call this function";
        return false;
    }
    QMutexLocker locker( mutex );

    u8* oBuff = (u8*)oBuff32;
    oBuff[ 0 ] = 0x7e;
    oBuff[ 1 ] = 0x0a;
    oBuff[ 2 ] = 0x22;
    oBuff[ 3 ] = 0x5f;
    oBuff[ 4 ] = 0x7e;
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 5, &out2, e2Timeout );
    if( r < 0 )
    {
        WRN << "do_i3_bulk_transfer():" << r;
        return false;
    }
    if( memcmp( dlModeSuccessResponse, dBuff, 5 ) )
    {
        WRN << "unexpected reset response" << QByteArray( (const char*)dBuff, 5 );
        hexdump( dBuff, 5 );
        return false;
    }
    Close();
    return true;
}

#define LCD_MAIN_WIDTH   320
#define LCD_MAIN_HEIGHT  480

#define SCREEN_SHOT_PACK_LEN  1024 * 2

typedef enum
{
    SCREEN_SHOT_BK_CTRL = 0,
    SCREEN_SHOT_LCD_BUF,
    SCREEN_SHOT_SECTION_LCD_BUF
} diag_screen_shot_sub_cmd_type;


struct lcd_buf_info_type
{
    u8  is_main_lcd;
    u16     x;
    u16     y;
    u16     width;
    u16     height;
    u32    total_bytes;
    u32    sended_bytes;
    bool  update;
    bool  updated;
    bool  packed;
    bool  is_fast_mode;
    bool  full_draw;
    u8 buftmp[LCD_MAIN_WIDTH * LCD_MAIN_HEIGHT * 4];
    u8 buf[LCD_MAIN_WIDTH * LCD_MAIN_HEIGHT * 2];
} __attribute__(( packed ));


struct diag_lcd_backlight_ctrl_req_type
{
    u8    cmd_code;
    u8    sub_cmd_code;
    bool main_onoff;
    u8    main_value;
    bool sub_onoff;
    u8    sub_value;
    bool ok;
}__attribute__(( packed ));


enum sequence_flow_type
{
    SEQ_START = 0,
    SEQ_GET_BUF,
    SEQ_REGET_BUF,
    SEQ_GET_BUF_COMPLETED,
    SEQ_GET_BUF_SUSPEND,
    SEQ_STOP
};


struct diag_lcd_get_buf_req_type
{
    u8     cmd_code;
    u8     sub_cmd_code;
    bool  ok;
    bool  is_main_lcd;
    u16     x;
    u16     y;
    u16     width;
    u16     height;
    u8     seq_flow;
    u32    total_bytes;
    u32    sended_bytes;
    bool  packed;
    bool  is_fast_mode;
    bool  full_draw;
    u8     buf[SCREEN_SHOT_PACK_LEN];
}__attribute__(( packed ));


union diag_screen_shot_type
{
    u8 cmd_code;
    diag_lcd_backlight_ctrl_req_type lcd_bk_ctrl;
    diag_lcd_get_buf_req_type lcd_buf;
}__attribute__(( packed ));

void PrintLCDStatus( diag_lcd_get_buf_req_type* lcd )
{
    DBG;
#define TELL( _x_ ) qDebug() << #_x_ << ::hex << lcd->_x_

    TELL( cmd_code );
    TELL( sub_cmd_code );
    TELL( ok );
    TELL( is_main_lcd );
    TELL( x );
    TELL( y );
    TELL( width );
    TELL( height );
    TELL( seq_flow );
    TELL( total_bytes );
    TELL( sended_bytes );
    TELL( packed );
    TELL( is_fast_mode );
    TELL( full_draw );
#undef TELL
}

QByteArray PhoneHandler::GetLCD()
{
    QByteArray ret( 0x4b000, Qt::Uninitialized );

    QMutexLocker locker( mutex );
    u8* oBuff = (u8*)oBuff32;

    u32 s = sizeof( diag_lcd_get_buf_req_type );
    u8 mBuf[ sizeof( diag_lcd_get_buf_req_type ) + 4 ];
    diag_lcd_get_buf_req_type *req = (diag_lcd_get_buf_req_type*)&mBuf[0];
    diag_lcd_get_buf_req_type *rep = (diag_lcd_get_buf_req_type*)dBuff;

    // clear buffers
    memset( mBuf, 0, sizeof( mBuf ) );
    memset( dBuff, 0, sizeof( dBuff ) );
    //memset( oBuff32, 0, sizeof( oBuff32 ) );

    // form request
    req->cmd_code = 0x96;// DIAG_LGF_SCREEN_SHOT_F
    req->sub_cmd_code = SCREEN_SHOT_LCD_BUF;
    req->seq_flow = SEQ_START;

    // escape it
    EscapeMessage( mBuf, oBuff, s );

    // terminate it
    oBuff[ s - 1 ] = 0x7e;

    // create checksum
    DoFcs16( oBuff, s - 3 );

    // send request

    //qDebug() << "in:";
    //hexdump( oBuff, s );

    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, s, &out2, e2Timeout );
    if( r < 0 || out != 0x1b )
    {
        WRN << "do_i3_bulk_transfer():" << r << out;
        hexdump( oBuff, s );
        hexdump( (u8*)dBuff, out );
        return QByteArray();
    }

    //qDebug() << "out" << out;
    //hexdump( oBuff, s );
    //hexdump( (u8*)dBuff, out );

    //PrintLCDStatus( rep );
    if( rep->width != 320 || rep->height != 480 )
    {
        WRN << "unexpected size";
        PrintLCDStatus( rep );
        return QByteArray();
    }

    // no need to copy values from the reply to the request like bytes copied
    // the kernel module keeps track of that by itself.  so just create 1 read request and keep sending it over and over
    // it saves having to keep calculating the checksum and all that crap

    // read it
    req->seq_flow = SEQ_GET_BUF;

    // escape it
    s = sizeof( diag_lcd_get_buf_req_type );
    EscapeMessage( mBuf, oBuff, s );

    // terminate it
    oBuff[ s - 1 ] = 0x7e;

    // create checksum
    DoFcs16( oBuff, s - 3 );

    u32 cnt = 0x4b000 / 0x800;// 0x96
    int pos = 0;
    u8* dat = (u8*)ret.constData();
    for( u32 i = 0; i < cnt; i++, pos += 0x800 )
    {
        // do the read
        r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, s, &out2, e2Timeout );
        if( r < 0 || out < 0x81b )
        {
            WRN << "do_i3_bulk_transfer():" << r << out << i;
            hexdump( oBuff, s );
            hexdump( (u8*)dBuff, out );
            return QByteArray();
        }

        if( out > 0x81b )// just assume that if teh length is exactly what we need, then it doesnt need unescaped
        {
            UnEscapeMessage( (u8*)dBuff, out );
            if( out != 0x81b )
            {
                WRN << "packet error" << out;
                return QByteArray();
            }
        }

        // screw checking the crc here, if anything, we just get a funny spot in the image

        // copy out the section we want
        memcpy( dat + pos, rep->buf, 0x800 );
    }

    // no need to issue any cancel commands on error or when finished.
    // the kernel module doesn't allocate any extra memory or resources that will be freed
    return ret;
}

PhoneHandler::DeviceMode PhoneHandler::FindDevice()
{
    // close existing device handle if there is one
    Close();
    QMutexLocker locker( mutex );

    // open device by vid/pid
    devh = libusb_open_device_with_vid_pid( NULL, VENDOR, PRODUCT );
    if( !devh )
    {
        return NoDevice;
    }

    // claim interface 2 and 3
    int r;
    bool dlMode = false;
    for( int i = 0; i < 4; i++ )
    {
        if( i == 1 )
        {
            continue;
        }
        if( libusb_kernel_driver_active( devh, i ) == 1  && ( r = libusb_detach_kernel_driver( devh, i ) ) < 0 )
        {
            WRN << "libusb_detach_kernel_driver(" << i << ") failed (" << r << ")";
            return NoDevice;
        }

        if( ( r = libusb_claim_interface( devh, i ) ) < 0 )
        {
            // this happens in download mode, but not Diag Mode, apparently
            if( i == 3 && r == LIBUSB_ERROR_NOT_FOUND )
            {
                dlMode = true;
                break;
            }
            WRN << "libusb_claim_interface(" << i << ") failed (" << r << ")";
            return NoDevice;
        }
    }

    // clear features
    //libusb_control_transfer( devh, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_ENDPOINT,
    //                                 LIBUSB_REQUEST_CLEAR_FEATURE, 0, 0x83, NULL, 0, e3Timeout );
    //libusb_control_transfer( devh, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_ENDPOINT,
    //                                 LIBUSB_REQUEST_CLEAR_FEATURE, 0, 0x02, NULL, 0, e3Timeout );

    // now try and send any commands to the phone to initialize it
    if( dlMode )
    {
        // looks like the thing may be in download mode.
        // poll it and see
        if( !PollDownloadMode() )
        {
            WRN << "PollDownloadMode() failed";
            return NoDevice;
        }

        // the phone says its in download mode, i'm inclined to believe it
        mode = DL_Mode;
        DBG << "Found a device running in Download Mode";
        return DL_Mode;
    }

    // looks like the phone may be running in diag mode.  if it is, then these commands should be successful
    u8* oBuff = (u8*)oBuff32;
    oBuff[ 0 ] = 0x00;
    oBuff[ 1 ] = 0x78;
    oBuff[ 2 ] = 0xf0;
    oBuff[ 3 ] = 0x7e;
    r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 4, &out2, e2Timeout );
    if( r < 0 || out != 0x3a )
    {
        // this happens right when you reboot the phone
        if( r != LIBUSB_ERROR_NO_DEVICE )
        {
            WRN << "check diag failed" << r << out;
            hexdump( &dBuff, out );
        }
        return NoDevice;
    }
    //hexdump( &dBuff, out );
    QString txt( ((const char*)dBuff) + 1 );
    txt.resize( 0x2f );

    oBuff[ 0 ] = 0x11;
    oBuff[ 1 ] = 0x17;
    oBuff[ 2 ] = 0x00;
    oBuff[ 3 ] = 0x06;
    oBuff[ 4 ] = 0x62;
    oBuff[ 5 ] = 0x4f;
    oBuff[ 6 ] = 0x7e;
    r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 7, &out2, e2Timeout );
    if( r < 0 )
    {
        WRN << "get MSL failed" << r;
        return NoDevice;
    }
    //hexdump( ((u8*)dBuff) + 4, 6 );
    //hexdump( &dBuff, out );
    QString msl( ((char*)dBuff) + 4 );
    msl.resize( 6 );
    DBG << "Found a device running in Diag Mode";
    DBG << "MSL:" << msl;

    // looks like the FW build date or something
    DBG << qPrintable( txt );

    mode = Diag_Mode;
    return Diag_Mode;
}

u32 PhoneHandler::GetEsn()
{
    QMutexLocker locker( mutex );
    u32 req = qToBigEndian( 0x0c143a7e );
    int r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, (u8*)&req, sizeof( u32 ), &out2, e2Timeout );
    if( r < 0 || out < 4 + 4 + 2 + 1 )
    {
        WRN << "do_i3_bulk_transfer():" << r << out;
        return 0;
    }
    qDebug() << "Electronic Serial Number:" << qPrintable( QString( "%1 [%2]" )
                                                           .arg( dBuff[ 1 ] )
                                                           .arg( dBuff[ 1 ], 8, 16, QChar( '0' ) ) );
    return dBuff[ 1 ];
}

bool PhoneHandler::GetMemoryRegions( QList< MemRegion > &regions )
{
    regions.clear();
    int r;
    QMutexLocker locker( mutex );

    u8* oBuff = (u8*)oBuff32;
    oBuff[ 0 ] = 0x7e;
    oBuff[ 1 ] = 0x10;
    oBuff[ 2 ] = 0xf9;
    oBuff[ 3 ] = 0xe0;
    oBuff[ 4 ] = 0x7e;
    r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, oBuff, 5, &out2, e2Timeout );
    if( r < 0 )
    {
        WRN << "do_i3_bulk_transfer():" << r;
        return false;
    }
    if( !ParseMemoryRegions( (u8*)dBuff, out, regions ) )
    {
        regions.clear();
        return false;
    }
    return true;
}

bool PhoneHandler::ParseMemoryRegions( const u8* buf, u32 len, QList< MemRegion > &regions )
{
    u32 outSize;
    u32 pos = 3;

    if( len < 6 + MemRegion::MinBuffSize )
    {
        WRN << "too small" << len;
        return false;
    }

    u16 bLen =  *(u16*)( buf + 1 );
    bLen = (( bLen >> 8 ) & 0xff ) | (( bLen << 8 ) & 0xff00 );
    if( bLen != len - 6 )
    {
        WRN << "bad length" << bLen << len;
        return false;
    }

    do
    {
        MemRegion r;
        if( !r.Load( buf + pos, len - pos, outSize ) )
        {
            WRN << " error loading data";
            return false;
        }
        regions.push_back( r );
        pos += outSize;
    }
    while( pos + MemRegion::MinBuffSize < len );
    return true;
}

QByteArray PhoneHandler::DumpMemory( u32 addr, u32 len )
{
    if( len > MaxMemChunkSize )
    {
        WRN << "len is too big" << len;
        return QByteArray();
    }
    u32 dBuff[ 4096 / sizeof( u32 ) ];
    u8 escapedBuf[ sizeof( InRequest ) * 2 ];
    int out;
    int out2;
    int r;

    // create request
    InRequest ir;
    ir.type = 0x7e;
    ir.sig = qToBigEndian( 0x14125634 );
    ir.offset = qToBigEndian( addr );
    ir.len = qToBigEndian( len );
    ir.endTag = 0x7e;

    // do checksum
    DoFcsInRequest( &ir );

    u32 l = EscapeInRequest( &ir, escapedBuf );

    //hexdump( &ir, sizeof( InRequest ) );

    QMutexLocker locker( mutex );
    r = do_i3_bulk_transfer( devh, E3_BulkIn, (u8*)dBuff, 4096, &out, e3Timeout, E2_BulkOut, escapedBuf, l, &out2, e2Timeout );
    if( r < 0 )
    {
        WRN << "Read failed (" << r << ")";
        return QByteArray();
    }
    if( (u32)out != sizeof( MemDumpResponse ) + len )
    {
        WRN << "Short read" << out;
        hexdump( &ir, sizeof( InRequest ) );
        hexdump( dBuff, sizeof( MemDumpResponse ) );
        return QByteArray();
    }

    MemDumpResponse *resp = (MemDumpResponse*)dBuff;
    if( resp->sig != qToBigEndian( (u32)0x15125634 )
            || resp->len != qToBigEndian( len )
            || resp->offset != qToBigEndian( addr ) )
    {
        WRN << "Bad read response";
        hexdump( resp, 0x50 );
        return QByteArray();
    }
    QByteArray ret( resp->memory, len );
    return ret;
}

MemDumpThread::MemDumpThread( PhoneHandler* phone, QObject *parent ) :
    QThread( parent ), ph( phone ), abort( false )
{
}

MemDumpThread::~MemDumpThread()
{
    CancelDump();
    wait();
}

void MemDumpThread::CancelDump()
{
    mutex.lock();
    abort = true;
    mutex.unlock();
}

bool MemDumpThread::DumpRegion( u32 addr, u32 len, const QString &path )
{
    QMutexLocker locker( &mutex );
    if ( !isRunning() )
    {
        abort = false;
        address = addr;
        size = len;
        if( !path.size() )
        {
            return false;
        }
        else
        {
            fName = path;
        }
        locker.unlock();
        start( NormalPriority );
    }
    else
    {
        emit SendFatalError( "thread is already running" );
        return false;
    }
    return true;
}

void MemDumpThread::run()
{
        if ( abort )
        {
            qDebug( "Thread abort" );
            return;
        }

        bool fail = false;

        emit SendProgress( 0 );


        mutex.lock();

        // create output file
        QFile file( fName );
        if( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
        {
            emit SendFatalError( QString( "Can't open file for writing: \"%1\"" ).arg( fName ) );
            fail = true;
        }

        // copy these so the mutex can be unlocked
        u32 addrp = address;
        u32 sizep = size;

        mutex.unlock();
        if( fail )
        {
            return;
        }

        // start doing stuff
        const u32 chunksize = 0xff0;
        const u32 chunkCnt = RU( sizep, chunksize ) / chunksize;
        u32 i = 0;
        u32 left = sizep;
        u32 addr = addrp;

        do
        {
            u32 toRead = MIN( chunksize, left );

            QByteArray ba = ph->DumpMemory( addr, toRead );
            if( ba.isEmpty() )
            {
                emit SendFatalError( QString( "Error reading chunk %1 of %2" ).arg( i ).arg( chunkCnt ) );
                file.close();
                return;
            }
            if( file.write( ba ) != ba.size() )
            {
                emit SendFatalError( QString( "Error writing chunk %1 of %2" ).arg( i ).arg( chunkCnt ) );
                file.close();
                return;
            }

            i++;
            float progress = (((float)i / (float)chunkCnt) * 100.0f);
            emit SendProgress( (int)progress );

            addr += toRead;
            left -= toRead;

            // check if abort has been called
            bool exitNow;
            mutex.lock();
            exitNow = abort;
            mutex.unlock();
            if( exitNow )
            {
                file.close();
                emit SendFatalError( QString( "Dump Canceled on chunk %1 of %2" ).arg( i ).arg( chunkCnt ) );
                return;
            }
        }
        while( left );

        file.close();

        emit SendProgress( 100 );
        emit SendDone( addrp, sizep, fName );
}

